﻿using System;
using System.Collections.Generic;

using System.Text;
using RoughJs.Helper;

namespace RoughJs.Ast
{
    /// <summary>
    /// 2010-01-5 ----- 2010-01-29
    /// 陈曦 1.0
    /// Microsoft Public License (Ms-PL)
    ///This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
    ///1. Definitions
    ///The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
    ///A "contribution" is the original software, or any additions or changes to the software.
    ///A "contributor" is any person that distributes its contribution under this license.
    ///"Licensed patents" are a contributor's patent claims that read directly on its contribution.
    ///2. Grant of Rights
    ///(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
    ///(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
    ///3. Conditions and Limitations
    ///(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
    ///(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
    ///(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
    ///(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
    ///(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 
    /// </summary>
    public class NativeArray : IdScriptableObject
    {

        private const int
       Id_constructor = 1,
       Id_toString = 2,
       Id_toLocaleString = 3,
       Id_toSource = 4,
       Id_join = 5,
       Id_reverse = 6,
       Id_sort = 7,
       Id_push = 8,
       Id_pop = 9,
       Id_shift = 10,
       Id_unshift = 11,
       Id_splice = 12,
       Id_concat = 13,
       Id_slice = 14,
       Id_indexOf = 15,
       Id_lastIndexOf = 16,
       Id_every = 17,
       Id_filter = 18,
       Id_forEach = 19,
       Id_map = 20,
       Id_some = 21,

       MAX_PROTOTYPE_ID = 21;

        // #/string_id_map#

        private const int
            ConstructorId_join = -Id_join,
            ConstructorId_reverse = -Id_reverse,
            ConstructorId_sort = -Id_sort,
            ConstructorId_push = -Id_push,
            ConstructorId_pop = -Id_pop,
            ConstructorId_shift = -Id_shift,
            ConstructorId_unshift = -Id_unshift,
            ConstructorId_splice = -Id_splice,
            ConstructorId_concat = -Id_concat,
            ConstructorId_slice = -Id_slice,
            ConstructorId_indexOf = -Id_indexOf,
            ConstructorId_lastIndexOf = -Id_lastIndexOf,
            ConstructorId_every = -Id_every,
            ConstructorId_filter = -Id_filter,
            ConstructorId_forEach = -Id_forEach,
            ConstructorId_map = -Id_map,
            ConstructorId_some = -Id_some;

        /**
         * Internal representation of the JavaScript array's length property.
         */
        private long length;

        /**
         * Fast storage for dense arrays. Sparse arrays will use the superclass's
         * hashtable storage scheme.
         */
        private Object[] dense;

        /**
         * True if all numeric properties are stored in <code>dense</code>.
         */
        private bool denseOnly;

        /**
         * The maximum size of <code>dense</code> that will be allocated initially.
         */
        private static int maximumInitialCapacity = 10000;

        /**
         * The default capacity for <code>dense</code>.
         */
        private static int DEFAULT_INITIAL_CAPACITY = 10;

        /**
         * The factor to grow <code>dense</code> by.
         */
        private static double GROW_FACTOR = 1.5;
        private static int MAX_PRE_GROW_SIZE = (int)(int.MaxValue / GROW_FACTOR);

/*
     * Optimization possibilities and open issues:
     * - Long vs. double schizophrenia.  I suspect it might be better
     * to use double throughout.
     *
     * - Functions that need a new Array call "new Array" in the
     * current scope rather than using a hardwired constructor;
     * "Array" could be redefined.  It turns out that js calls the
     * equivalent of "new Array" in the current scope, except that it
     * always gets at least an object back, even when Array == null.
     */

    private static Object ARRAY_TAG = "Array";
    private static int NEGATIVE_ONE = -1;

   public static void init(Scriptable scope, bool sealed1)
    {
        NativeArray obj = new NativeArray(0);
        obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed1);
    }
    
   public static int getMaximumInitialCapacity() {
        return maximumInitialCapacity;
    }

   public static void setMaximumInitialCapacity(int maximumInitialCapacity) {
        NativeArray.maximumInitialCapacity = maximumInitialCapacity;
    }

    public NativeArray(long lengthArg)
    {
        denseOnly = lengthArg <= maximumInitialCapacity;
        if (denseOnly) {
            int intLength = (int) lengthArg;
            if (intLength < DEFAULT_INITIAL_CAPACITY)
                intLength = DEFAULT_INITIAL_CAPACITY;
            dense = new Object[intLength];
            for(int i=0;i<intLength;i++){
                dense[i]=ScriptableObject.NOT_FOUND;
            }
           // Arrays.fill(dense, );
        }
        length = lengthArg;
    }

    public NativeArray(Object[] array)
    {
        denseOnly = true;
        dense = array;
        length = array.Length;
    }


    public override String getClassName()
    {
        return "Array";
    }

    private static int
        Id_length        =  1,
        MAX_INSTANCE_ID  =  1;

    
    public override int getMaxInstanceId()
    {
        return MAX_INSTANCE_ID;
    }


    public override int findInstanceIdInfo(String s)
    {
        if (s=="length") {
            return instanceIdInfo(DONTENUM | PERMANENT, Id_length);
        }
        return base.findInstanceIdInfo(s);
    }


    public override String getInstanceIdName(int id)
    {
        if (id == Id_length) { return "length"; }
        return base.getInstanceIdName(id);
    }


    public override Object getInstanceIdValue(int id)
    {
        if (id == Id_length) {
            return ScriptRuntime.wrapNumber(length);
        }
        return base.getInstanceIdValue(id);
    }


    public override void setInstanceIdValue(int id, Object value)
    {
        if (id == Id_length) {
            setLength(value); return;
        }
        base.setInstanceIdValue(id, value);
    }
    
    
    public override  void fillConstructorProperties(IdFunctionObject ctor)
    {
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_join,
                "join", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reverse,
                "reverse", 1);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_sort,
                "sort", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_push,
                "push", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_pop,
                "pop", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_shift,
                "shift", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_unshift,
                "unshift", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_splice,
                "splice", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_concat,
                "concat", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_slice,
                "slice", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_indexOf,
                "indexOf", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_lastIndexOf,
                "lastIndexOf", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_every,
                "every", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_filter,
                "filter", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_forEach,
                "forEach", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_map,
                "map", 2);
        addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_some,
                "some", 2);
        base.fillConstructorProperties(ctor);
    }


    public  override void initPrototypeId(int id)
    {
        String s;
        int arity;
        switch (id) {
          case Id_constructor:    arity=1; s="constructor";    break;
          case Id_toString:       arity=0; s="toString";       break;
          case Id_toLocaleString: arity=1; s="toLocaleString"; break;
          case Id_toSource:       arity=0; s="toSource";       break;
          case Id_join:           arity=1; s="join";           break;
          case Id_reverse:        arity=0; s="reverse";        break;
          case Id_sort:           arity=1; s="sort";           break;
          case Id_push:           arity=1; s="push";           break;
          case Id_pop:            arity=1; s="pop";            break;
          case Id_shift:          arity=1; s="shift";          break;
          case Id_unshift:        arity=1; s="unshift";        break;
          case Id_splice:         arity=1; s="splice";         break;
          case Id_concat:         arity=1; s="concat";         break;
          case Id_slice:          arity=1; s="slice";          break;
          case Id_indexOf:        arity=1; s="indexOf";        break;
          case Id_lastIndexOf:    arity=1; s="lastIndexOf";    break;
          case Id_every:          arity=1; s="every";          break;
          case Id_filter:         arity=1; s="filter";         break;
          case Id_forEach:        arity=1; s="forEach";        break;
          case Id_map:            arity=1; s="map";            break;
          case Id_some:           arity=1; s="some";           break;
          default: throw new ArgumentException(id.ToString());
        }
        initPrototypeMethod(ARRAY_TAG, id, s, arity);
    }


    public override Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
                             Scriptable thisObj, Object[] args)
    {
        if (!f.hasTag(ARRAY_TAG)) {
            return base.execIdCall(f, cx, scope, thisObj, args);
        }
        int id = f.methodId();
      again:
        for (;;) {
            switch (id) {
              case ConstructorId_join:
              case ConstructorId_reverse:
              case ConstructorId_sort:
              case ConstructorId_push:
              case ConstructorId_pop:
              case ConstructorId_shift:
              case ConstructorId_unshift:
              case ConstructorId_splice:
              case ConstructorId_concat:
              case ConstructorId_slice:
              case ConstructorId_indexOf:
              case ConstructorId_lastIndexOf:
              case ConstructorId_every:
              case ConstructorId_filter:
              case ConstructorId_forEach:
              case ConstructorId_map:
              case ConstructorId_some: {
                thisObj = ScriptRuntime.toObject(scope, args[0]);
                Object[] newArgs = new Object[args.Length-1];
                for (int i=0; i < newArgs.Length; i++)
                    newArgs[i] = args[i+1];
                args = newArgs;
                id = -id;
                goto again;
              }
    
              case Id_constructor: {
                bool inNewExpr = (thisObj == null);
                if (!inNewExpr) {
                    // IdFunctionObject.construct will set up parent, proto
                    return f.construct(cx, scope, args);
                }
                return jsConstructor(cx, scope, args);
              }
    
              case Id_toString:
                return toStringHelper(cx, scope, thisObj,
                    cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE), false);
    
              case Id_toLocaleString:
                return toStringHelper(cx, scope, thisObj, false, true);
    
              case Id_toSource:
                return toStringHelper(cx, scope, thisObj, true, false);
    
              case Id_join:
                return js_join(cx, thisObj, args);
    
              case Id_reverse:
                return js_reverse(cx, thisObj, args);
    
              case Id_sort:
                return js_sort(cx, scope, thisObj, args);
    
              case Id_push:
                return js_push(cx, thisObj, args);
    
              case Id_pop:
                return js_pop(cx, thisObj, args);
    
              case Id_shift:
                return js_shift(cx, thisObj, args);
    
              case Id_unshift:
                return js_unshift(cx, thisObj, args);
    
              case Id_splice:
                return js_splice(cx, scope, thisObj, args);
    
              case Id_concat:
                return js_concat(cx, scope, thisObj, args);
    
              case Id_slice:
                return js_slice(cx, thisObj, args);
    
              case Id_indexOf:
                return indexOfHelper(cx, thisObj, args, false);
    
              case Id_lastIndexOf:
                return indexOfHelper(cx, thisObj, args, true);
    
              case Id_every:
              case Id_filter:
              case Id_forEach:
              case Id_map:
              case Id_some:
                return iterativeMethod(cx, id, scope, thisObj, args);
            }
            throw new ArgumentException(id.ToString());
        }
    }


    public override Object get(int index, Scriptable start)
    {
        if (!denseOnly && isGetterOrSetter(null, index, false))
            return base.get(index, start);
        if (dense != null && 0 <= index && index < dense.Length)
            return dense[index];
        return base.get(index, start);
    }


    public override bool has(int index, Scriptable start)
    {
        if (!denseOnly && isGetterOrSetter(null, index, false))
            return base.has(index, start);
        if (dense != null && 0 <= index && index < dense.Length)
            return dense[index] != NOT_FOUND;
        return base.has(index, start);
    }

    // if id is an array index (ECMA 15.4.0), return the number,
    // otherwise return -1L
    private static long toArrayIndex(String id)
    {
        double d = ScriptRuntime.toNumber(id);
        if (!Double.IsNaN(d)) {
            long index = ScriptRuntime.toUint32(d);
            if (index == d && index != 4294967295L) {
                // Assume that ScriptRuntime.toString(index) is the same
                // as java.lang.Long.toString(index) for long
                if (index.ToString()==id) {
                    return index;
                }
            }
        }
        return -1;
    }


    public override void put(String id, Scriptable start, Object value)
    {
        base.put(id, start, value);
        if (start == this) {
            // If the object is sealed, super will throw exception
            long index = toArrayIndex(id);
            if (index >= length) {
                length = index + 1;
                denseOnly = false;
            }
        }
    }

    private bool ensureCapacity(int capacity)
    {
        if (capacity > dense.Length) {
            if (capacity > MAX_PRE_GROW_SIZE) {
                denseOnly = false;
                return false;
            }
            capacity = Math.Max(capacity, (int)(dense.Length * GROW_FACTOR));
            Object[] newDense = new Object[capacity];
            Array.Copy(dense, 0, newDense, 0, dense.Length);
            Arrays.fill(newDense, dense.Length, newDense.Length,
                        ScriptableObject.NOT_FOUND);
            dense = newDense;
        }
        return true;
    }


    public override void put(int index, Scriptable start, Object value)
    {
        if (start == this && !isSealed() && dense != null && 0 <= index &&
            (denseOnly || !isGetterOrSetter(null, index, true)))
        {
            if (index < dense.Length) {
                dense[index] = value;
                if (this.length <= index)
                    this.length = (long)index + 1;
                return;
            } else if (denseOnly && index < dense.Length * GROW_FACTOR &&
                       ensureCapacity(index+1))
            {
                dense[index] = value;
                this.length = (long)index + 1;
                return;
            } else {
                denseOnly = false;
            }
        }
        base.put(index, start, value);
        if (start == this) {
            // only set the array length if given an array index (ECMA 15.4.0)
            if (this.length <= index) {
                // avoid overflowing index!
                this.length = (long)index + 1;
            }
        }
    }


    public override void delete(int index)
    {
        if (dense != null && 0 <= index && index < dense.Length &&
            !isSealed() && (denseOnly || !isGetterOrSetter(null, index, true)))
        {
            dense[index] = NOT_FOUND;
        } else {
            base.delete(index);
        }
    }


    public override Object[] getIds()
    {
        Object[] superIds = base.getIds();
        if (dense == null) { return superIds; }
        int N = dense.Length;
        long currentLength = length;
        if (N > currentLength) {
            N = (int)currentLength;
        }
        if (N == 0) { return superIds; }
        int superLength = superIds.Length;
        Object[] ids = new Object[N + superLength];

        int presentCount = 0;
        for (int i = 0; i != N; ++i) {
            // Replace existing elements by their indexes
            if (dense[i] != NOT_FOUND) {
                ids[presentCount] = i;
                ++presentCount;
            }
        }
        if (presentCount != N) {
            // dense contains deleted elems, need to shrink the result
            Object[] tmp = new Object[presentCount + superLength];
            Array.Copy(ids, 0, tmp, 0, presentCount);
            ids = tmp;
        }
        Array.Copy(superIds, 0, ids, presentCount, superLength);
        return ids;
    }


    public override Object getDefaultValue(Type hint)
    {
        if (hint == ScriptRuntime.NumberClass) {
            Context cx = Context.getContext();
            if (cx.getLanguageVersion() == Context.VERSION_1_2)
                return length;
        }
        return base.getDefaultValue(hint);
    }

    /**
     * See ECMA 15.4.1,2
     */
    private static Object jsConstructor(Context cx, Scriptable scope,
                                        Object[] args)
    {
        if (args.Length == 0)
            return new NativeArray(0);

        // Only use 1 arg as first element for version 1.2; for
        // any other version (including 1.3) follow ECMA and use it as
        // a length.
        if (cx.getLanguageVersion() == Context.VERSION_1_2) {
            return new NativeArray(args);
        } else {
            Object arg0 = args[0];
            if (args.Length > 1 || !(ScriptRuntime.isNumber(arg0)))
            {
                return new NativeArray(args);
            } else {
                long len = ScriptRuntime.toUint32(arg0);
                if (len != (ScriptRuntime.toNumber(arg0)))
                    throw Context.reportRuntimeError0("msg.arraylength.bad");
                return new NativeArray(len);
            }
        }
    }

    public long getLength() {
        return length;
    }

    /** @deprecated Use {@link #getLength()} instead. */
    public long jsGet_length() {
        return getLength();
    }
    
    /**
     * Change the value of the internal flag that determines whether all
     * storage is handed by a dense backing array rather than an associative
     * store.
     * @param denseOnly new value for denseOnly flag
     * @throws IllegalArgumentException if an attempt is made to enable
     *   denseOnly after it was disabled; NativeArray code is not written
     *   to handle switching back to a dense representation
     */
    public void setDenseOnly( bool denseOnly) {
        if (denseOnly && !this.denseOnly)
            throw new ArgumentException();
        this.denseOnly = denseOnly;
    }

    private void setLength(Object val) {
        /* XXX do we satisfy this?
         * 15.4.5.1 [[Put]](P, V):
         * 1. Call the [[CanPut]] method of A with name P.
         * 2. If Result(1) is false, return.
         * ?
         */

        double d = ScriptRuntime.toNumber(val);
        long longVal = ScriptRuntime.toUint32(d);
        if (longVal != d)
            throw Context.reportRuntimeError0("msg.arraylength.bad");

        if (denseOnly) {
            if (longVal < length) {
                // downcast okay because denseOnly
                Arrays.fill(dense, (int) longVal, dense.Length, NOT_FOUND);
                length = longVal;
                return;
            } else if (longVal < MAX_PRE_GROW_SIZE &&
                       longVal < (length * GROW_FACTOR) &&
                       ensureCapacity((int)longVal))
            {
                length = longVal;
                return;
            } else {
                denseOnly = false;
            }
        }
        if (longVal < length) {
            // remove all properties between longVal and length
            if (length - longVal > 0x1000) {
                // assume that the representation is sparse
                Object[] e = getIds(); // will only find in object itself
                for (int i=0; i < e.Length; i++) {
                    Object id = e[i];
                    if (id is String) {
                        // > MAXINT will appear as string
                        String strId = (String)id;
                        long index = toArrayIndex(strId);
                        if (index >= longVal)
                            delete(strId);
                    } else {
                        int index = Convert.ToInt32(id);
                        if (index >= longVal)
                            delete(index);
                    }
                }
            } else {
                // assume a dense representation
                for (long i = longVal; i < length; i++) {
                    deleteElem(this, i);
                }
            }
        }
        length = longVal;
    }

    /* Support for generic Array-ish objects.  Most of the Array
     * functions try to be generic; anything that has a length
     * property is assumed to be an array.
     * getLengthProperty returns 0 if obj does not have the length property
     * or its value is not convertible to a number.
     */
   public static long getLengthProperty(Context cx, Scriptable obj) {
        // These will both give numeric lengths within Uint32 range.
        if (obj is NativeString) {
            return ((NativeString)obj).getLength();
        } else if (obj is NativeArray) {
            return ((NativeArray)obj).getLength();
        }
        return ScriptRuntime.toUint32(
            ScriptRuntime.getObjectProp(obj, "length", cx));
    }

    private static Object setLengthProperty(Context cx, Scriptable target,
                                            long length)
    {
        return ScriptRuntime.setObjectProp(
                   target, "length", ScriptRuntime.wrapNumber(length), cx);
    }

    /* Utility functions to encapsulate index > Integer.MAX_VALUE
     * handling.  Also avoids unnecessary object creation that would
     * be necessary to use the general ScriptRuntime.get/setElem
     * functions... though this is probably premature optimization.
     */
    private static void deleteElem(Scriptable target, long index) {
        int i = (int)index;
        if (i == index) { target.delete(i); }
        else { target.delete(index.ToString()); }
    }

    private static Object getElem(Context cx, Scriptable target, long index)
    {
        if (index > int.MaxValue) {
            String id = (index).ToString();
            return ScriptRuntime.getObjectProp(target, id, cx);
        } else {
            return ScriptRuntime.getObjectIndex(target, (int)index, cx);
        }
    }

    private static void setElem(Context cx, Scriptable target, long index,
                                Object value)
    {
        if (index > int.MaxValue)
        {
            String id = (index).ToString();
            ScriptRuntime.setObjectProp(target, id, value, cx);
        } else {
            ScriptRuntime.setObjectIndex(target, (int)index, value, cx);
        }
    }

    private static String toStringHelper(Context cx, Scriptable scope,
                                         Scriptable thisObj,
                                         bool toSource, bool toLocale)
    {
        /* It's probably redundant to handle long lengths in this
         * function; StringBuilders are limited to 2^31 in java.
         */

        long length = getLengthProperty(cx, thisObj);

        StringBuilder result = new StringBuilder(256);

        // whether to return '4,unquoted,5' or '[4, "quoted", 5]'
        String separator;

        if (toSource) {
            result.Append('[');
            separator = ", ";
        } else {
            separator = ",";
        }

        bool haslast = false;
        long i = 0;

        bool toplevel, iterating;
        if (cx.iterating == null) {
            toplevel = true;
            iterating = false;
            cx.iterating = new ObjToIntMap(31);
        } else {
            toplevel = false;
            iterating = cx.iterating.has(thisObj);
        }

        // Make sure cx.iterating is set to null when done
        // so we don't leak memory
        try {
            if (!iterating) {
                cx.iterating.put(thisObj, 0); // stop recursion.
                for (i = 0; i < length; i++) {
                    if (i > 0) result.Append(separator);
                    Object elem = getElem(cx, thisObj, i);
                    if (elem == null || elem == Undefined.instance) {
                        haslast = false;
                        continue;
                    }
                    haslast = true;

                    if (toSource) {
                        result.Append(ScriptRuntime.uneval(cx, scope, elem));

                    } else if (elem is String) {
                        String s = (String)elem;
                        if (toSource) {
                            result.Append('\"');
                            result.Append(ScriptRuntime.escapeString(s));
                            result.Append('\"');
                        } else {
                            result.Append(s);
                        }

                    } else {
                        if (toLocale)
                        {
                            Callable fun;
                            Scriptable funThis;
                            fun = ScriptRuntime.getPropFunctionAndThis(
                                      elem, "toLocaleString", cx);
                            funThis = ScriptRuntime.lastStoredScriptable(cx);
                            elem = fun.call(cx, scope, funThis,
                                            ScriptRuntime.emptyArgs);
                        }
                        result.Append(ScriptRuntime.toString(elem));
                    }
                }
            }
        } finally {
            if (toplevel) {
                cx.iterating = null;
            }
        }

        if (toSource) {
            //for [,,].Length behavior; we want toString to be symmetric.
            if (!haslast && i > 0)
                result.Append(", ]");
            else
                result.Append(']');
        }
        return result.ToString();
    }

    /**
     * See ECMA 15.4.4.3
     */
    private static String js_join(Context cx, Scriptable thisObj,
                                  Object[] args)
    {
        long llength = getLengthProperty(cx, thisObj);
        int length = (int)llength;
        if (llength != length) {
            throw Context.reportRuntimeError1(
                "msg.arraylength.too.big",llength.ToString());
        }
        // if no args, use "," as separator
        String separator = (args.Length < 1 || args[0] == Undefined.instance)
                           ? ","
                           : ScriptRuntime.toString(args[0]);
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly) {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < length; i++) {
                    if (i != 0) {
                        sb.Append(separator);
                    }
                    if (i < na.dense.Length) {
                        Object temp = na.dense[i];
                        if (temp != null && temp != Undefined.instance &&
                            temp != ScriptableObject.NOT_FOUND)
                        {
                            sb.Append(ScriptRuntime.toString(temp));
                        }
                    }
                }
                return sb.ToString();
            }
        }
        if (length == 0) {
            return "";
        }
        String[] buf = new String[length];
        int total_size = 0;
        for (int i = 0; i != length; i++) {
            Object temp = getElem(cx, thisObj, i);
            if (temp != null && temp != Undefined.instance) {
                String str = ScriptRuntime.toString(temp);
                total_size += str.Length;
                buf[i] = str;
            }
        }
        total_size += (length - 1) * separator.Length;
        StringBuilder sb1 = new StringBuilder(total_size);
        for (int i = 0; i != length; i++) {
            if (i != 0) {
                sb1.Append(separator);
            }
            String str = buf[i];
            if (str != null) {
                // str == null for undefined or null
                sb1.Append(str);
            }
        }
        return sb1.ToString();
    }

    /**
     * See ECMA 15.4.4.4
     */
    private static Scriptable js_reverse(Context cx, Scriptable thisObj,
                                         Object[] args)
    {
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly) {
                for (int i=0, j=((int)na.length)-1; i < j; i++,j--) {
                    Object temp = na.dense[i];
                    na.dense[i] = na.dense[j];
                    na.dense[j] = temp;
                }
                return thisObj;
            }
        }
        long len = getLengthProperty(cx, thisObj);

        long half = len / 2;
        for(long i=0; i < half; i++) {
            long j = len - i - 1;
            Object temp1 = getElem(cx, thisObj, i);
            Object temp2 = getElem(cx, thisObj, j);
            setElem(cx, thisObj, i, temp2);
            setElem(cx, thisObj, j, temp1);
        }
        return thisObj;
    }

    /**
     * See ECMA 15.4.4.5
     */
    private static Scriptable js_sort(Context cx, Scriptable scope,
                                      Scriptable thisObj, Object[] args)
    {
        long length = getLengthProperty(cx, thisObj);

        if (length <= 1) { return thisObj; }

        Object compare;
        Object[] cmpBuf;

        if (args.Length > 0 && Undefined.instance != args[0]) {
            // sort with given compare function
            compare = args[0];
            cmpBuf = new Object[2]; // Buffer for cmp arguments
        } else {
            // sort with default compare
            compare = null;
            cmpBuf = null;
        }
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly) {
                int ilength = (int) length;
                heapsort(cx, scope, na.dense, ilength, compare, cmpBuf);
                return thisObj;
            }
        }

        // Should we use the extended sort function, or the faster one?
        if (length >= int.MaxValue) {
            heapsort_extended(cx, scope, thisObj, length, compare, cmpBuf);
        } else {
            int ilength = (int)length;
            // copy the JS array into a working array, so it can be
            // sorted cheaply.
            Object[] working = new Object[ilength];
            for (int i = 0; i != ilength; ++i) {
                working[i] = getElem(cx, thisObj, i);
            }

            heapsort(cx, scope, working, ilength, compare, cmpBuf);

            // copy the working array back into thisObj
            for (int i = 0; i != ilength; ++i) {
                setElem(cx, thisObj, i, working[i]);
            }
        }
        return thisObj;
    }

    // Return true only if x > y
    private static bool isBigger(Context cx, Scriptable scope,
                                    Object x, Object y,
                                    Object cmp, Object[] cmpBuf)
    {
        if (cmp == null) {
            if (cmpBuf != null) Kit.codeBug();
        } else {
            if (cmpBuf == null || cmpBuf.Length != 2) Kit.codeBug();
        }

        Object undef = Undefined.instance;
        Object notfound = ScriptableObject.NOT_FOUND;

        // sort undefined to end
        if (y == undef || y == notfound) {
            return false; // x can not be bigger then undef
        } else if (x == undef || x == notfound) {
            return true; // y != undef here, so x > y
        }

        if (cmp == null) {
            // if no cmp function supplied, sort lexicographically
            String a = ScriptRuntime.toString(x);
            String b = ScriptRuntime.toString(y);
            return a.CompareTo(b) > 0;
        }
        else {
            // assemble args and call supplied JS cmp function
            cmpBuf[0] = x;
            cmpBuf[1] = y;
            Callable fun = ScriptRuntime.getValueFunctionAndThis(cmp, cx);
            Scriptable funThis = ScriptRuntime.lastStoredScriptable(cx);

            Object ret = fun.call(cx, scope, funThis, cmpBuf);
            double d = ScriptRuntime.toNumber(ret);

            // XXX what to do when cmp function returns NaN?  ECMA states
            // that it's then not a 'consistent comparison function'... but
            // then what do we do?  Back out and start over with the generic
            // cmp function when we see a NaN?  Throw an error?

            // for now, just ignore it:

            return d > 0;
        }
    }

/** Heapsort implementation.
 * See "Introduction to Algorithms" by Cormen, Leiserson, Rivest for details.
 * Adjusted for zero based indexes.
 */
    private static void heapsort(Context cx, Scriptable scope,
                                 Object[] array, int length,
                                 Object cmp, Object[] cmpBuf)
    {
        if (length <= 1) Kit.codeBug();

        // Build heap
        for (int i = length / 2; i != 0;) {
            --i;
            Object pivot = array[i];
            heapify(cx, scope, pivot, array, i, length, cmp, cmpBuf);
        }

        // Sort heap
        for (int i = length; i != 1;) {
            --i;
            Object pivot = array[i];
            array[i] = array[0];
            heapify(cx, scope, pivot, array, 0, i, cmp, cmpBuf);
        }
    }

/** pivot and child heaps of i should be made into heap starting at i,
 * original array[i] is never used to have less array access during sorting.
 */
    private static void heapify(Context cx, Scriptable scope,
                                Object pivot, Object[] array, int i, int end,
                                Object cmp, Object[] cmpBuf)
    {
        for (;;) {
            int child = i * 2 + 1;
            if (child >= end) {
                break;
            }
            Object childVal = array[child];
            if (child + 1 < end) {
                Object nextVal = array[child + 1];
                if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) {
                    ++child; childVal = nextVal;
                }
            }
            if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) {
                break;
            }
            array[i] = childVal;
            i = child;
        }
        array[i] = pivot;
    }

/** Version of heapsort that call getElem/setElem on target to query/assign
 * array elements instead of Java array access
 */
    private static void heapsort_extended(Context cx, Scriptable scope,
                                          Scriptable target, long length,
                                          Object cmp, Object[] cmpBuf)
    {
        if (length <= 1) Kit.codeBug();

        // Build heap
        for (long i = length / 2; i != 0;) {
            --i;
            Object pivot = getElem(cx, target, i);
            heapify_extended(cx, scope, pivot, target, i, length, cmp, cmpBuf);
        }

        // Sort heap
        for (long i = length; i != 1;) {
            --i;
            Object pivot = getElem(cx, target, i);
            setElem(cx, target, i, getElem(cx, target, 0));
            heapify_extended(cx, scope, pivot, target, 0, i, cmp, cmpBuf);
        }
    }

    private static void heapify_extended(Context cx, Scriptable scope,
                                         Object pivot, Scriptable target,
                                         long i, long end,
                                         Object cmp, Object[] cmpBuf)
    {
        for (;;) {
            long child = i * 2 + 1;
            if (child >= end) {
                break;
            }
            Object childVal = getElem(cx, target, child);
            if (child + 1 < end) {
                Object nextVal = getElem(cx, target, child + 1);
                if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) {
                    ++child; childVal = nextVal;
                }
            }
            if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) {
                break;
            }
            setElem(cx, target, i, childVal);
            i = child;
        }
        setElem(cx, target, i, pivot);
    }

    /**
     * Non-ECMA methods.
     */

    private static Object js_push(Context cx, Scriptable thisObj,
                                  Object[] args)
    {
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly &&
                na.ensureCapacity((int) na.length + args.Length))
            {
                for (int i = 0; i < args.Length; i++) {
                    na.dense[(int)na.length++] = args[i];
                }
                return ScriptRuntime.wrapNumber(na.length);
            }
        }
        long length = getLengthProperty(cx, thisObj);
        for (int i = 0; i < args.Length; i++) {
            setElem(cx, thisObj, length + i, args[i]);
        }

        length += args.Length;
        Object lengthObj = setLengthProperty(cx, thisObj, length);

        /*
         * If JS1.2, follow Perl4 by returning the last thing pushed.
         * Otherwise, return the new array length.
         */
        if (cx.getLanguageVersion() == Context.VERSION_1_2)
            // if JS1.2 && no arguments, return undefined.
            return args.Length == 0
                ? Undefined.instance
                : args[args.Length - 1];

        else
            return lengthObj;
    }

    private static Object js_pop(Context cx, Scriptable thisObj,
                                 Object[] args)
    {
        Object result;
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly && na.length > 0) {
                na.length--;
                result = na.dense[(int)na.length];
                na.dense[(int)na.length] = NOT_FOUND;
                return result;
            }
        }
        long length = getLengthProperty(cx, thisObj);
        if (length > 0) {
            length--;

            // Get the to-be-deleted property's value.
            result = getElem(cx, thisObj, length);

            // We don't need to delete the last property, because
            // setLength does that for us.
        } else {
            result = Undefined.instance;
        }
        // necessary to match js even when length < 0; js pop will give a
        // length property to any target it is called on.
        setLengthProperty(cx, thisObj, length);

        return result;
    }

    private static Object js_shift(Context cx, Scriptable thisObj,
                                   Object[] args)
    {
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly && na.length > 0) {
                na.length--;
                Object result = na.dense[0];
                Array.Copy(na.dense, 1, na.dense, 0, (int)na.length);
                na.dense[(int)na.length] = NOT_FOUND;
                return result;
            }
        }
        Object result1;
        long length = getLengthProperty(cx, thisObj);
        if (length > 0) {
            long i = 0;
            length--;

            // Get the to-be-deleted property's value.
            result1 = getElem(cx, thisObj, i);

            /*
             * Slide down the array above the first element.  Leave i
             * set to point to the last element.
             */
            if (length > 0) {
                for (i = 1; i <= length; i++) {
                    Object temp = getElem(cx, thisObj, i);
                    setElem(cx, thisObj, i - 1, temp);
                }
            }
            // We don't need to delete the last property, because
            // setLength does that for us.
        } else {
            result1 = Undefined.instance;
        }
        setLengthProperty(cx, thisObj, length);
        return result1;
    }

    private static Object js_unshift(Context cx, Scriptable thisObj,
                                     Object[] args)
    {
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly &&
                na.ensureCapacity((int)na.length + args.Length))
            {
                Array.Copy(na.dense, 0, na.dense, args.Length,
                                 (int) na.length);
                for (int i = 0; i < args.Length; i++) {
                    na.dense[i] = args[i];
                }
                na.length += args.Length;
                return ScriptRuntime.wrapNumber(na.length);
            }
        }
        long length = getLengthProperty(cx, thisObj);
        int argc = args.Length;

        if (args.Length > 0) {
            /*  Slide up the array to make room for args at the bottom */
            if (length > 0) {
                for (long last = length - 1; last >= 0; last--) {
                    Object temp = getElem(cx, thisObj, last);
                    setElem(cx, thisObj, last + argc, temp);
                }
            }

            /* Copy from argv to the bottom of the array. */
            for (int i = 0; i < args.Length; i++) {
                setElem(cx, thisObj, i, args[i]);
            }

            /* Follow Perl by returning the new array length. */
            length += args.Length;
            return setLengthProperty(cx, thisObj, length);
        }
        return ScriptRuntime.wrapNumber(length);
    }

    private static Object js_splice(Context cx, Scriptable scope,
                                    Scriptable thisObj, Object[] args)
    {
    	NativeArray na = null;
    	 bool denseMode = false;
        if (thisObj is NativeArray) {
            na = (NativeArray) thisObj;
            denseMode = na.denseOnly;
        }

        /* create an empty Array to return. */
        scope = getTopLevelScope(scope);
        int argc = args.Length;
        if (argc == 0)
            return ScriptRuntime.newObject(cx, scope, "Array", null);
        long length = getLengthProperty(cx, thisObj);

        /* Convert the first argument into a starting index. */
        long begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length);
        argc--;

        /* Convert the second argument into count */
        long count;
        if (args.Length == 1) {
            count = length - begin;
        } else {
            double dcount = ScriptRuntime.toInteger(args[1]);
            if (dcount < 0) {
                count = 0;
            } else if (dcount > (length - begin)) {
                count = length - begin;
            } else {
                count = (long)dcount;
            }
            argc--;
        }

        long end = begin + count;

        /* If there are elements to remove, put them into the return value. */
        Object result;
        if (count != 0) {
            if (count == 1
                && (cx.getLanguageVersion() == Context.VERSION_1_2))
            {
                /*
                 * JS lacks "list context", whereby in Perl one turns the
                 * single scalar that's spliced out into an array just by
                 * assigning it to @single instead of $single, or by using it
                 * as Perl push's first argument, for instance.
                 *
                 * JS1.2 emulated Perl too closely and returned a non-Array for
                 * the single-splice-out case, requiring callers to test and
                 * wrap in [] if necessary.  So JS1.3, default, and other
                 * versions all return an array of length 1 for uniformity.
                 */
                result = getElem(cx, thisObj, begin);
            } else {
            	if (denseMode) {
                    int intLen = (int) (end - begin);
                    Object[] copy = new Object[intLen];
                    Array.Copy(na.dense, (int) begin, copy, 0, intLen);
                    result = cx.newArray(scope, copy);
            	} else {
            		Scriptable resultArray = ScriptRuntime.newObject(cx, scope,
                        "Array", null);
                    for (long last = begin; last != end; last++) {
                        Object temp = getElem(cx, thisObj, last);
                        setElem(cx, resultArray, last - begin, temp);
                    }
                    result = resultArray;
            	}
            }
        } else { // (count == 0)
        	if (cx.getLanguageVersion() == Context.VERSION_1_2) {
                /* Emulate C JS1.2; if no elements are removed, return undefined. */
                result = Undefined.instance;
            } else {
            	result = ScriptRuntime.newObject(cx, scope, "Array", null);
        	}
        }

        /* Find the direction (up or down) to copy and make way for argv. */
        long delta = argc - count;
        if (denseMode && length + delta < int.MaxValue &&
            na.ensureCapacity((int) (length + delta)))
        {
            Array.Copy(na.dense, (int) end, na.dense,
                             (int) (begin + argc), (int) (length - end));
            if (argc > 0) {
                Array.Copy(args, 2, na.dense, (int) begin, argc);
            }
            if (delta < 0) {
                Arrays.fill(na.dense, (int) (length + delta), (int) length,
                            NOT_FOUND);
            }
            na.length = length + delta;
            return result;
        }

        if (delta > 0) {
            for (long last = length - 1; last >= end; last--) {
                Object temp = getElem(cx, thisObj, last);
                setElem(cx, thisObj, last + delta, temp);
            }
        } else if (delta < 0) {
            for (long last = end; last < length; last++) {
                Object temp = getElem(cx, thisObj, last);
                setElem(cx, thisObj, last + delta, temp);
            }
        }

        /* Copy from argv into the hole to complete the splice. */
        int argoffset = args.Length - argc;
        for (int i = 0; i < argc; i++) {
            setElem(cx, thisObj, begin + i, args[i + argoffset]);
        }

        /* Update length in case we deleted elements from the end. */
        setLengthProperty(cx, thisObj, length + delta);
        return result;
    }

    /*
     * See Ecma 262v3 15.4.4.4
     */
    private static Scriptable js_concat(Context cx, Scriptable scope,
                                        Scriptable thisObj, Object[] args)
    {
        // create an empty Array to return.
        scope = getTopLevelScope(scope);
        Function ctor = ScriptRuntime.getExistingCtor(cx, scope, "Array");
        Scriptable result = ctor.construct(cx, scope, ScriptRuntime.emptyArgs);
        if (thisObj is NativeArray && result is NativeArray) {
            NativeArray denseThis = (NativeArray) thisObj;
            NativeArray denseResult = (NativeArray) result;
            if (denseThis.denseOnly && denseResult.denseOnly) {
                // First calculate length of resulting array
                bool canUseDense = true;
                int length = (int)denseThis.length;
                for (int i = 0; i < args.Length && canUseDense; i++) {
                    if (args[i] is NativeArray) {
                        // only try to use dense approach for Array-like
                        // objects that are actually NativeArrays
                        NativeArray arg = (NativeArray) args[i];
                        canUseDense = arg.denseOnly;
                        length += (int)arg.length;
                    } else {
                        length++;
                    }
                }
                if (canUseDense && denseResult.ensureCapacity(length)) {
                    Array.Copy(denseThis.dense, 0, denseResult.dense,
                                     0, (int)denseThis.length);
                    int cursor = (int)denseThis.length;
                    for (int i = 0; i < args.Length && canUseDense; i++) {
                        if (args[i] is NativeArray) {
                            NativeArray arg = (NativeArray) args[i];
                            Array.Copy(arg.dense, 0,
                                    denseResult.dense, cursor,
                                    (int)arg.length);
                            cursor += (int)arg.length;
                        } else {
                            denseResult.dense[cursor++] = args[i];
                        }
                    }
                    denseResult.length = length;
                    return result;
                }
            }
        }

        long length1;
        long slot = 0;

        /* Put the target in the result array; only add it as an array
         * if it looks like one.
         */
        if (ScriptRuntime.instanceOf(thisObj, ctor, cx)) {
            length1 = getLengthProperty(cx, thisObj);

            // Copy from the target object into the result
            for (slot = 0; slot < length1; slot++) {
                Object temp = getElem(cx, thisObj, slot);
                setElem(cx, result, slot, temp);
            }
        } else {
            setElem(cx, result, slot++, thisObj);
        }

        /* Copy from the arguments into the result.  If any argument
         * has a numeric length property, treat it as an array and add
         * elements separately; otherwise, just copy the argument.
         */
        for (int i = 0; i < args.Length; i++) {
            if (ScriptRuntime.instanceOf(args[i], ctor, cx)) {
                // ScriptRuntime.instanceOf => is Scriptable
                Scriptable arg = (Scriptable)args[i];
                length1 = getLengthProperty(cx, arg);
                for (long j = 0; j < length1; j++, slot++) {
                    Object temp = getElem(cx, arg, j);
                    setElem(cx, result, slot, temp);
                }
            } else {
                setElem(cx, result, slot++, args[i]);
            }
        }
        return result;
    }

    private Scriptable js_slice(Context cx, Scriptable thisObj,
                                Object[] args)
    {
        Scriptable scope = getTopLevelScope(this);
        Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null);
        long length = getLengthProperty(cx, thisObj);

        long begin, end;
        if (args.Length == 0) {
            begin = 0;
            end = length;
        } else {
            begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length);
            if (args.Length == 1) {
                end = length;
            } else {
                end = toSliceIndex(ScriptRuntime.toInteger(args[1]), length);
            }
        }

        for (long slot = begin; slot < end; slot++) {
            Object temp = getElem(cx, thisObj, slot);
            setElem(cx, result, slot - begin, temp);
        }

        return result;
    }

    private static long toSliceIndex(double value, long length) {
        long result;
        if (value < 0.0) {
            if (value + length < 0.0) {
                result = 0;
            } else {
                result = (long)(value + length);
            }
        } else if (value > length) {
            result = length;
        } else {
            result = (long)value;
        }
        return result;
    }

    /**
     * Implements the methods "indexOf" and "lastIndexOf".
     */
    private Object indexOfHelper(Context cx, Scriptable thisObj,
                                 Object[] args, bool isLast)
    {
        Object compareTo = args.Length > 0 ? args[0] : Undefined.instance;
        long length = getLengthProperty(cx, thisObj);
        long start;
        if (isLast) {
            // lastIndexOf
            /*
             * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf
             * The index at which to start searching backwards. Defaults to the
             * array's length, i.e. the whole array will be searched. If the
             * index is greater than or equal to the length of the array, the
             * whole array will be searched. If negative, it is taken as the
             * offset from the end of the array. Note that even when the index
             * is negative, the array is still searched from back to front. If
             * the calculated index is less than 0, -1 is returned, i.e. the
             * array will not be searched.
             */
            if (args.Length < 2) {
                // default
                start = length-1;
            } else {
                start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1]));
                if (start >= length)
                    start = length-1;
                else if (start < 0)
                    start += length;
                // Note that start may be negative, but that's okay
                // as the result of -1 will fall out from the code below
            }
        } else {
            // indexOf
            /*
             * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
             * The index at which to begin the search. Defaults to 0, i.e. the
             * whole array will be searched. If the index is greater than or
             * equal to the length of the array, -1 is returned, i.e. the array
             * will not be searched. If negative, it is taken as the offset from
             * the end of the array. Note that even when the index is negative,
             * the array is still searched from front to back. If the calculated
             * index is less than 0, the whole array will be searched.
             */
            if (args.Length < 2) {
                // default
                start = 0;
            } else {
                start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1]));
                if (start < 0) {
                    start += length;
                    if (start < 0)
                        start = 0;
                }
                // Note that start may be > length-1, but that's okay
                // as the result of -1 will fall out from the code below
            }
        }
        if (thisObj is NativeArray) {
            NativeArray na = (NativeArray) thisObj;
            if (na.denseOnly) {
                if (isLast) {
                  for (int i=(int)start; i >= 0; i--) {
                      if (na.dense[i] != ScriptableObject.NOT_FOUND &&
                          ScriptRuntime.shallowEq(na.dense[i], compareTo))
                      {
                          return (long)i;
                      }
                  }
                } else {
                  for (int i=(int)start; i < length; i++) {
                      if (na.dense[i] != ScriptableObject.NOT_FOUND &&
                          ScriptRuntime.shallowEq(na.dense[i], compareTo))
                      {
                          return (long)(i);
                      }
                  }
                }
                return NEGATIVE_ONE;
            }
        }
        if (isLast) {
          for (long i=start; i >= 0; i--) {
              if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) {
                  return (long)(i);
              }
          }
        } else {
          for (long i=start; i < length; i++) {
              if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) {
                  return (long)(i);
              }
          }
        }
        return NEGATIVE_ONE;
    }

    /**
     * Implements the methods "every", "filter", "forEach", "map", and "some".
     */
    private Object iterativeMethod(Context cx, int id, Scriptable scope, 
                                   Scriptable thisObj, Object[] args)
    {
        Object callbackArg = args.Length > 0 ? args[0] : Undefined.instance;
        if (callbackArg == null || !(callbackArg is Function)) {
            throw ScriptRuntime.notFunctionError(
                     ScriptRuntime.toString(callbackArg));
        }
        Function f = (Function) callbackArg;
        Scriptable parent = ScriptableObject.getTopLevelScope(f);
        Scriptable thisArg;
        if (args.Length < 2 || args[1] == null || args[1] == Undefined.instance)
        {
            thisArg = parent;
        } else {
            thisArg = ScriptRuntime.toObject(cx, scope, args[1]);
        }
        long length = getLengthProperty(cx, thisObj);
        Scriptable array = ScriptRuntime.newObject(cx, scope, "Array", null);
        long j=0;
        for (long i=0; i < length; i++) {
            Object[] innerArgs = new Object[3];
            Object elem = (i > int.MaxValue)
                ? ScriptableObject.getProperty(thisObj, i.ToString())
                : ScriptableObject.getProperty(thisObj, (int)i);
            if (elem == ScriptableObject.NOT_FOUND) {
                continue;
            }
            innerArgs[0] = elem;
            innerArgs[1] = i;
            innerArgs[2] = thisObj;
            Object result = f.call(cx, parent, thisArg, innerArgs);
            switch (id) {
              case Id_every:
                if (!ScriptRuntime.toBoolean(result))
                    return false;
                break;
              case Id_filter:
                if (ScriptRuntime.toBoolean(result))
                  setElem(cx, array, j++, innerArgs[0]);
                break;
              case Id_forEach:
                break;
              case Id_map:
                setElem(cx, array, i, result);
                break;
              case Id_some:
                if (ScriptRuntime.toBoolean(result))
                    return true;
                break;
            }
        }
        switch (id) {
          case Id_every:
            return true;
          case Id_filter:
          case Id_map:
            return array;
          case Id_some:
            return false;
          case Id_forEach:
          default:
            return Undefined.instance;
        }
    }

// #string_id_map#


    public override int findPrototypeId(String s)
    {
        int id;
// #generated# Last update: 2005-09-26 15:47:42 EDT
        id = 0; String X = null; int c;
            switch (s.Length) {
            case 3: c=s.ToCharArray()[0];
                if (c=='m') { if (s.ToCharArray()[2]=='p' && s.ToCharArray()[1]=='a') {id=Id_map; goto L0;} }
                else if (c=='p') { if (s.ToCharArray()[2]=='p' && s.ToCharArray()[1]=='o') {id=Id_pop; goto L0;} }
                goto L;
            case 4: switch (s.ToCharArray()[2]) {
                case 'i': X="join";id=Id_join; goto L;
                case 'm': X="some";id=Id_some; goto L;
                case 'r': X="sort";id=Id_sort; goto L;
                case 's': X="push";id=Id_push; goto L;
                } goto L;
            case 5: c=s.ToCharArray()[1];
                if (c=='h') { X="shift";id=Id_shift; }
                else if (c=='l') { X="slice";id=Id_slice; }
                else if (c=='v') { X="every";id=Id_every; }
                goto L;
            case 6: c=s.ToCharArray()[0];
                if (c=='c') { X="concat";id=Id_concat; }
                else if (c=='f') { X="filter";id=Id_filter; }
                else if (c=='s') { X="splice";id=Id_splice; }
                goto L;
            case 7: switch (s.ToCharArray()[0]) {
                case 'f': X="forEach";id=Id_forEach; goto L;
                case 'i': X="indexOf";id=Id_indexOf; goto L;
                case 'r': X="reverse";id=Id_reverse; goto L;
                case 'u': X="unshift";id=Id_unshift; goto L;
                } goto L;
            case 8: c=s.ToCharArray()[3];
                if (c=='o') { X="toSource";id=Id_toSource; }
                else if (c=='t') { X="toString";id=Id_toString; }
                goto L;
            case 11: c=s.ToCharArray()[0];
                if (c=='c') { X="constructor";id=Id_constructor; }
                else if (c=='l') { X="lastIndexOf";id=Id_lastIndexOf; }
                goto L;
            case 14: X="toLocaleString";id=Id_toLocaleString; goto L;
            }
         L:;
            if (X!=null && X!=s && !(X==s)) id = 0;
        L0: ;
// #/generated#
        return id;
    }
    
   
    }
}
